using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Text;

using Kenly.DBFramework.Reflection;

namespace Kenly.DBFramework.Gateway
{

    /// <summary>
    /// Makes a condition of a SQL statement according to the column name and column value.
    /// </summary> 
    [Serializable]
    public sealed class Expression
    {
        private string _currentExpString;
        private string _combinedExpString;

        //private bool _isValid;
        private string _columnName;
        private ArrayList _currentValues = new ArrayList();

        private Expression _nextSibling;

        /// <summary>
        /// Default Expression indicates the sql expression 1=1.
        /// </summary>
        public static readonly string DefaultExpression = " 1=1 ";

        /// <summary>
        /// Invalid Expression indicates the sql expression null=null.
        /// </summary>
        public static readonly string InvalidExpression = " null=null ";


        /// <summary>
        /// Indicates whether the expression contains a valid condition expression.
        /// </summary>
        public bool IsValid
        {
            get
            {
                return (this.CurrentExpression != Expression.InvalidExpression) &&
                    (!this.CombinedExpression.Contains(Expression.InvalidExpression));
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public string CombinedExpression
        {
            get
            {
                return _combinedExpString;
            }
            private set
            {
                _combinedExpString = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public string CurrentExpression
        {
            get
            {
                return _currentExpString;
            }
            private set
            {
                _currentExpString = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public string CurrentColumn
        {
            get
            {
                return _columnName;
            }
            private set
            {
                _columnName = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public IList CurrentValues
        {
            get
            {
                return _currentValues;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public Expression NextSibling
        {
            get
            {
                return _nextSibling;
            }
            private set
            {
                _nextSibling = value;
            }
        }



        /// <summary>
        /// 
        /// </summary>
        internal Expression()
        {
            //this.IsValid = false;
            this.CurrentExpression = Expression.InvalidExpression;
            this.CombinedExpression = this.CurrentExpression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        public Expression(string expression)
        {
            //this.IsValid = !string.IsNullOrEmpty(expression);
            if (string.IsNullOrEmpty(expression))
            {
                expression = Expression.InvalidExpression;
            }

            this.CurrentExpression = expression;
            this.CombinedExpression = this.CurrentExpression;
        }

        /// <summary>
        /// Default Expression indicates the sql expression 1=1.
        /// </summary>
        /// <returns></returns>
        public static Expression CreateDefault()
        {
            return new Expression(Expression.DefaultExpression);
        }

        /// <summary>
        /// Invalid Expression indicates the sql expression null=null.
        /// </summary>
        /// <returns></returns>
        public static Expression CreateInvalid()
        {
            return new Expression(Expression.InvalidExpression);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="columnValue"></param>
        /// <returns></returns>
        public static Expression Equal(string columnName, object columnValue)
        {
            return BuildExpression(ExpressionType.Equal, columnName, columnValue);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="columnValue"></param>
        /// <returns></returns>
        public static Expression Greater(string columnName, object columnValue)
        {
            return BuildExpression(ExpressionType.Greater, columnName, columnValue);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="columnValue"></param>
        /// <returns></returns>
        public static Expression GreaterEqual(string columnName, object columnValue)
        {
            return BuildExpression(ExpressionType.GreaterEqual, columnName, columnValue);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="columnValue"></param>
        /// <returns></returns>
        public static Expression Less(string columnName, object columnValue)
        {
            return BuildExpression(ExpressionType.Less, columnName, columnValue);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="columnValue"></param>
        /// <returns></returns>
        public static Expression LessEqual(string columnName, object columnValue)
        {
            return BuildExpression(ExpressionType.LessEqual, columnName, columnValue);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="columnValue"></param>
        /// <returns></returns>
        public static Expression NotEqual(string columnName, object columnValue)
        {
            return BuildExpression(ExpressionType.NotEqual, columnName, columnValue);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="columnValue"></param>
        /// <returns></returns>
        public static Expression Like(string columnName, object columnValue)
        {
            return BuildExpression(ExpressionType.Like, columnName, columnValue);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="columnValue"></param>
        /// <returns></returns>
        public static Expression NotLike(string columnName, object columnValue)
        {
            return BuildExpression(ExpressionType.NotLike, columnName, columnValue);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="rangeValues"></param>
        /// <returns></returns>
        public static Expression In(string columnName, IList rangeValues)
        {
            return BuildInRangeExpression(columnName, rangeValues);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="rangeValues"></param>
        /// <returns></returns>
        public static Expression NotIn(string columnName, IList rangeValues)
        {
            return BuildNotInRangeExpression(columnName, rangeValues);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="startValue"></param>
        /// <param name="endValue"></param>
        /// <returns></returns>
        public static Expression Between(string columnName, object startValue, object endValue)
        {
            Expression expression = new Expression();
            if (string.IsNullOrEmpty(columnName) || startValue == null || endValue == null)
            {
                return expression;
            }

            expression.CurrentExpression = columnName + " BETWEEN " +
                BuildValueExpression(startValue) + " AND " + BuildValueExpression(endValue);
            expression.CombinedExpression = expression.CurrentExpression;
            //expression.IsValid = true;

            expression.CurrentColumn = columnName;
            expression.CurrentValues.Add(startValue);
            expression.CurrentValues.Add(endValue);

            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public static Expression IsNull(string columnName)
        {
            Expression expression = new Expression();
            if (string.IsNullOrEmpty(columnName))
            {
                return expression;
            }
            expression.CurrentExpression = columnName + " is null ";
            expression.CombinedExpression = expression.CurrentExpression;
            //expression.IsValid = true;

            expression.CurrentColumn = columnName;

            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public static Expression IsNotNull(string columnName)
        {
            Expression expression = new Expression();
            if (string.IsNullOrEmpty(columnName))
            {
                return expression;
            }
            expression.CurrentExpression = columnName + " is not null ";
            expression.CombinedExpression = expression.CurrentExpression;
            //expression.IsValid = true;

            expression.CurrentColumn = columnName;

            return expression;
        }

        private static Expression BuildExpression(ExpressionType expressionType, string columnName, object columnValue)
        {
            Expression expression = new Expression();
            if (string.IsNullOrEmpty(columnName) || columnValue == null)
            {
                return expression;
            }
            expression.CurrentExpression = columnName + " " + GetExpressionTypeString(expressionType) +
                " " + BuildValueExpression(columnValue);
            expression.CombinedExpression = expression.CurrentExpression;
            //expression.IsValid = true;

            expression.CurrentColumn = columnName;
            expression.CurrentValues.Add(columnValue);

            return expression;
        }

        private static Expression BuildInRangeExpression(string columnName, IList rangeValues)
        {
            Expression expression = new Expression();
            if (string.IsNullOrEmpty(columnName) || rangeValues == null || rangeValues.Count <= 0)
            {
                return expression;
            }
            expression.CurrentExpression = columnName + " IN(" + BuildRangeValueExpression(rangeValues) + ") ";
            expression.CombinedExpression = expression.CurrentExpression;
            //expression.IsValid = true;

            expression.CurrentColumn = columnName;
            expression._currentValues.AddRange(rangeValues);

            return expression;
        }

        private static Expression BuildNotInRangeExpression(string columnName, IList rangeValues)
        {
            Expression expression = new Expression();
            if (string.IsNullOrEmpty(columnName) || rangeValues == null || rangeValues.Count <= 0)
            {
                return expression;
            }
            expression.CurrentExpression = columnName +
                " NOT IN(" + BuildRangeValueExpression(rangeValues) + ") ";
            expression.CombinedExpression = expression.CurrentExpression;
            //expression.IsValid = true;

            expression.CurrentColumn = columnName;
            expression._currentValues.AddRange(rangeValues);

            return expression;
        }

        private static string BuildRangeValueExpression(IList rangeValues)
        {
            StringBuilder sb = new StringBuilder();
            System.Collections.IEnumerator enumerator = rangeValues.GetEnumerator();
            while (enumerator.MoveNext())
            {
                sb.Append(BuildValueExpression(enumerator.Current) + ",");
            }
            if (sb.Length > 0 && sb.ToString().EndsWith(","))
            {
                sb.Remove(sb.Length - 1, 1);
            }
            return sb.ToString();
        }

        internal static string BuildValueExpression(object columnValue)
        {
            if (columnValue.GetType() == typeof(string))
            {
                return "'" + columnValue.ToString() + "'";
            }
            else if (columnValue.GetType() == typeof(DateTime))
            {
                DateTime newDateTime = DateTime.Parse(columnValue.ToString(), CultureInfo.CurrentCulture);
                return "'" + newDateTime.ToString(DateTimeFormat()) + "'";
            }
            else if (columnValue.GetType() == typeof(bool))
            {
                int flag = bool.Parse(columnValue.ToString()) ? 1 : 0;
                return flag.ToString(CultureInfo.CurrentCulture);
            }
            else
            {
                return columnValue.ToString();
            }
        }

        private static string DateTimeFormat()
        {
            return "yyyy-MM-dd HH:mm:ss";
        }

        private static string GetExpressionTypeString(ExpressionType expressionType)
        {
            string expressionTypeString = "=";
            switch (expressionType)
            {
                case ExpressionType.Equal: expressionTypeString = "=";
                    break;
                case ExpressionType.NotEqual: expressionTypeString = "<>";
                    break;
                case ExpressionType.Greater: expressionTypeString = ">";
                    break;
                case ExpressionType.GreaterEqual: expressionTypeString = ">=";
                    break;
                case ExpressionType.Less: expressionTypeString = "<";
                    break;
                case ExpressionType.LessEqual: expressionTypeString = "<=";
                    break;
                case ExpressionType.Like: expressionTypeString = "LIKE";
                    break;
                case ExpressionType.NotLike: expressionTypeString = "NOT LIKE";
                    break;
                case ExpressionType.In: expressionTypeString = "IN";
                    break;
                case ExpressionType.NotIn: expressionTypeString = "NOT IN";
                    break;
                default: expressionTypeString = "=";
                    break;
            }
            return expressionTypeString;
        }

        #region Operator

        /// <summary>
        /// 
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        public static Expression operator &(Expression left, Expression right)
        {
            if (left == null)
            {
                throw new GatewayException(MsgResource.InvalidArguments);
            }
            if (right == null)
            {
                throw new GatewayException(MsgResource.InvalidArguments);
            }
            return BitwiseAnd(left, right);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        public static Expression BitwiseAnd(Expression left, Expression right)
        {
            CheckArgs(left, right);

            left.CombinedExpression = "(" + left.CombinedExpression + " AND " + right.CombinedExpression + ")";

            SetNextSibling(ref left, right);

            return left;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        public static Expression operator |(Expression left, Expression right)
        {
            if (left == null || right == null)
            {
                throw new GatewayException(MsgResource.InvalidArguments);
            }
            return BitwiseOr(left, right);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        public static Expression BitwiseOr(Expression left, Expression right)
        {
            CheckArgs(left, right);

            left.CombinedExpression = "(" + left.CombinedExpression + " OR " + right.CombinedExpression + ")";

            SetNextSibling(ref left, right);

            return left;
        }

        private static void CheckArgs(Expression left, Expression right)
        {
            if (left == null)
            {
                throw new GatewayException(MsgResource.InvalidArguments);
            }
            if (right == null)
            {
                throw new GatewayException(MsgResource.InvalidArguments);
            }
        }


        //private string GetAndOrWithNext(Expression expression)
        //{
        //    return expression.IsAndWithNext ? " AND " : " OR ";
        //}

        private static void SetNextSibling(ref Expression left, Expression right)
        {
            Expression lastNextSibling = GetLastNextSibling(left);
            if (lastNextSibling == null)
            {
                left.NextSibling = right;
            }
            else
            {
                lastNextSibling.NextSibling = right;
            }
        }

        private static Expression GetLastNextSibling(Expression expression)
        {
            if (expression == null)
            {
                return null;
            }

            if (expression.NextSibling == null)
            {
                return expression;
            }
            else
            {
                return GetLastNextSibling(expression.NextSibling);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static implicit operator string(Expression expression)
        {
            if (expression == null)
            {
                throw new GatewayException(MsgResource.InvalidArguments);
            }

            return expression.ToString();
        }



        #endregion


        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.IsNullOrEmpty(this.CombinedExpression) ?
                Expression.InvalidExpression : this.CombinedExpression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public string ToOracleString()
        {
            string dateTimeFormat = DateTimeFormat();
            string oracleDateTimeFormat = "yyyy-mm-dd hh24:mi:ss";

            return ToOracleString(dateTimeFormat, oracleDateTimeFormat);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="oracleDateTimeFormat"></param>
        /// <returns></returns>
        public string ToOracleString(string oracleDateTimeFormat)
        {
            string dateTimeFormat = DateTimeFormat();
            if (string.IsNullOrEmpty(oracleDateTimeFormat))
            {
                oracleDateTimeFormat = "yyyy-mm-dd hh24:mi:ss";
            }

            return ToOracleString(dateTimeFormat, oracleDateTimeFormat);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private string ToOracleString(string dateTimeFormat, string oracleDateTimeFormat)
        {
            string combinedExpression = string.IsNullOrEmpty(this.CombinedExpression) ?
               Expression.InvalidExpression : this.CombinedExpression;

            if (!string.IsNullOrEmpty(this.CombinedExpression))
            {
                List<string> oldStringSet = new List<string>();
                ToOracleString(ref combinedExpression, this,
                    dateTimeFormat, oracleDateTimeFormat, ref oldStringSet);
            }

            return combinedExpression;
        }


        private static void ToOracleString(ref string combinedExpression, Expression expression,
            string dateTimeFormat, string oracleDateTimeFormat, ref List<string> oldStringSet)
        {
            if (expression == null || string.IsNullOrEmpty(combinedExpression))
            {
                return;
            }

            if (string.IsNullOrEmpty(dateTimeFormat))
            {
                dateTimeFormat = DateTimeFormat();
            }

            ReplaceWithOracleString(ref combinedExpression, expression,
                dateTimeFormat, oracleDateTimeFormat, ref oldStringSet);

            if (expression.NextSibling == null)
            {
                return;
            }
            else
            {
                ToOracleString(ref combinedExpression, expression.NextSibling,
                    dateTimeFormat, oracleDateTimeFormat, ref oldStringSet);
            }
        }

        private static void ReplaceWithOracleString(ref string combinedExpression, Expression expression,
            string dateTimeFormat, string oracleDateTimeFormat, ref List<string> oldStringSet)
        {
            foreach (object columnValue in expression.CurrentValues)
            {
                if (columnValue == null)
                {
                    continue;
                }

                ReplaceWithOracleStringByValue(ref combinedExpression, columnValue,
                    dateTimeFormat, oracleDateTimeFormat, ref oldStringSet);
            }
        }

        private static void ReplaceWithOracleStringByValue(ref string combinedExpression, object columnValue,
            string dateTimeFormat, string oracleDateTimeFormat, ref List<string> oldStringSet)
        {
            if (columnValue.GetType() != typeof(DateTime))
            {
                return;
            }

            DateTime date = DateTime.Parse(columnValue.ToString());
            string dateString = date.ToString(dateTimeFormat);
            string oldString = "'" + dateString + "'";

            if (oldStringSet.Contains(oldString))
            {
                return;
            }

            combinedExpression = combinedExpression.Replace(oldString,
                " to_date('" + dateString + "','" + oracleDateTimeFormat + "') ");

            oldStringSet.Add(oldString);
        }

    }
}
